package com.wdl.sm.webui.yang;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.nokia.taiji.yang.schema.Module;
import com.nokia.taiji.yang.schema.SchemaDataNode;
import com.nokia.taiji.yang.schema.SchemaNode;
import com.nokia.taiji.yang.schema.SchemaProcessor;
import com.nokia.taiji.yang.schema.YANG_Choice;
import com.nokia.taiji.yang.schema.YANG_Choice.YANG_Case;
import com.wdl.sm.webui.yang.node.ContainerNode;
import com.wdl.sm.webui.yang.node.ListNode;
import com.wdl.sm.webui.yang.node.NormalizedNode;

@Service
public class YangConfigCache extends SchemaProcessor {
    private static final Logger LOG = LoggerFactory.getLogger("YangConfigCache");

    String yangPath = "/opt/wdl/webui/yang";
    String jsonFilePath = "/opt/wdl/webui/config/";

    // store the table IDs that have been listed on the tree
    private static List<String> processedTables = new ArrayList<String>();

    // Top level elements map (dri <-> node for each tables)
    private static Map<String, SchemaDataNode> tableMap = new HashMap<String, SchemaDataNode>();

    @Override
    protected boolean ignore(Module module) {
        if (module.isSubModule() || "wdlYangExtensions".equals(module.getName())) {
            LOG.info("Ignoring module - " + module.getName());
            return true;
        } else {
            LOG.info("Module " + module.getName() + " is qualified to be processed.");
            return false;
        }
    }

    @Override
    protected void process(Module module) throws Exception {
        String moduleName = module.getName();

        LOG.info("Processing module:::::::::::" + moduleName);
        List<SchemaNode> topElements;

        // WARNING: naming conversion for common modules must end with "-common"!
        if (moduleName != null && moduleName.endsWith("-common")) {
            return;
        }

        topElements = module.getSchemaTree().getChildren();

        for (SchemaNode node : topElements) {
            if (!node.isDataNode()) {
                continue;
            }

            SchemaDataNode dataNode = (SchemaDataNode) node;

            String key = buildDriKey(module.getName(), dataNode.getSNI().toString());

            if (tableMap.containsKey(key)) {
                LOG.warn("A duplicated key was found: " + key);
            } else {
                tableMap.put(key, dataNode);
            }
        }
    }
    
    private static String buildDriKey(String module, String sni) {
        String path = sni;

        if (sni != null && sni.startsWith("/")) {
            path = sni.substring(1);
        }

        return "/" + module + ":" + path;
    }

    @Override
    protected void setup(String[] path) throws Exception {
        this.setInput(path[0]);
    }

    public synchronized void init() {
        // List the folders under yang config path
        File file = new File(yangPath);

        // build tableMap for top-level elements and navNodeList
        buildType(file);

        // generate schema and layout json files based on tableMap
        if (null != tableMap) {
          buildNormalizedTableMap(jsonFilePath, file.getName());
        }

        tableMap = null;

        YANGExt.cleanUp();
    }

    private static void buildNormalizedTableMap(String filePath, String type) {
        Gson gson = new GsonBuilder().setPrettyPrinting().create();

        List<String> keys = new ArrayList<String>();

        Map<String, Object> allSchema = new HashMap<String, Object>();

        // build up tables map => dri <-> NormalizedNode
        for (Entry<String, SchemaDataNode> entry : tableMap.entrySet()) {
            SchemaDataNode node = entry.getValue();
            String key = node.getID();

            if (keys.contains(key)) {
                LOG.error("Duplicated key was found " + key + " for " + type + "!!!");
                continue;
            }

            Object normalizedNode = YANGExt.buildWebuiYangNode(node);
            if (normalizedNode != null && normalizedNode instanceof NormalizedNode) {
                allSchema.put(key, normalizedNode);
            }
        }

        try {
            Map<String, Object> schema = new HashMap<String, Object>();
            schema.put("schema", allSchema);
            File schemaFile = new File(filePath, "schema.json");
            FileUtils.writeStringToFile(schemaFile, gson.toJson(schema));
        } catch (IOException e) {
            LOG.error("Failed to write data file for " + type + ": " + e);
        }

        allSchema = null;
    }

    public static String getNodeSni(SchemaNode node) {
        if (node.getSNI() == null) {
            LOG.warn("SNI is null for node " + node.getKeyWord());
            return "";
        }

        if (node instanceof YANG_Choice) {
            return node.getSNI().toString() + "/" + ((YANG_Choice) node).getID();
        } else if (node instanceof YANG_Case) {
            return node.getSNI().toString() + "/" + ((YANG_Choice) node.getParent()).getID() + "/" + ((YANG_Case) node).getID();
        } else {
            return node.getSNI().toString();
        }
    }

    private void buildType(File directory) {
        String type = directory.getName();
        LOG.info("Processing directory ======+" + type + "+======");

        String[] args = new String[1];
        try {
            args[0] = directory.getCanonicalPath();
            LOG.info("Processing directory ======+" + args[0] + "+======");
        } catch (IOException e) {
            LOG.error("Failed to get path for directory " + directory, e);
        }

        // initialize table map
        tableMap.clear();
        processedTables.clear();

        try {
            run(args);
        } catch (Exception e) {
            LOG.error("Failed to init schema repository for " + type, e);
        }

        LOG.info("Build nodes for " + type + " is done.");
    }

    public static SchemaDataNode getNode(String dri) {
        return tableMap.get(dri);
    }

    private static void retrieveDataNodes(Object inputNode, List<String> paths) {
        if (!(inputNode instanceof NormalizedNode)) {
            return;
        }

        NormalizedNode node = (NormalizedNode) inputNode;

        // check children
        List<Object> nextLevels = null;
        if (node instanceof ListNode) {
            nextLevels = ((ListNode) node).getElements();
        } else if (node instanceof ContainerNode) {
            nextLevels = ((ContainerNode) node).getElements();
        }

        if (nextLevels == null) {
            return;
        }

        for (Object nextLevel : nextLevels) {
            retrieveDataNodes(nextLevel, paths);
        }
    }

    public static List<String> buildExportedDataNodes(String sni, NormalizedNode node) {
        List<String> paths = new ArrayList<String>();

        retrieveDataNodes(node, paths);
        return paths;
    }
}
